import { Record } from './models';

const removeAttributes = (record: Record): { [key: string]: any } => {
  const clone = {
    ...record,
  };
  delete clone.attributes;
  return clone;
};

const fieldValueForCondition = (fieldValue: any): string => {
  switch (typeof fieldValue) {
    case 'string':
      return `'${fieldValue}'`;
    default:
      return `${fieldValue}`;
  }
};

export class SoqlBuilder {
  private fields: string[];
  private condition: Record;
  private sobjectType: string;
  private selectAll: boolean;
  private limitNumber: number;

  static fromRecord(record: Record): SoqlBuilder {
    return new SoqlBuilder().from(record.attributes.type).where(removeAttributes(record));
  }

  static select(fields: string[]): SoqlBuilder {
    return new SoqlBuilder().select(fields);
  }

  static selectAllFields() {
    return new SoqlBuilder().selectAllFields().limit(200);
  }

  private constructor() {
    this.fields = [];
    this.selectAll = false;
  }

  select(fields: string[]): SoqlBuilder {
    this.fields = fields;
    return this;
  }

  selectAllFields(): SoqlBuilder {
    this.selectAll = true;
    return this;
  }

  from(type: string): SoqlBuilder {
    this.sobjectType = type;
    return this;
  }

  where(record: Record): SoqlBuilder {
    this.condition = record;
    return this;
  }

  limit(limitNumber: number): SoqlBuilder {
    this.limitNumber = limitNumber;
    return this;
  }

  toSoql(): string {
    return this.appendLimitClause(this.appendCondition(this.appendFrom(this.selectFieldsToQuery())));
  }

  private selectFieldsToQuery(): string {
    return `SELECT ${this.getFieldList().join(', ')}`;
  }

  private appendFrom(query: string): string {
    return `${query} FROM ${this.sobjectType}`;
  }

  private appendCondition(query: string): string {
    return `${query} WHERE ${this.getConditionString()}`;
  }

  private appendLimitClause(query: string) {
    if (!this.limitNumber) {
      return query;
    }
    return `${query} LIMIT ${this.limitNumber}`;
  }

  private getConditionString(): string {
    return Object.entries(this.condition)
      .reduce((conditions, [fieldName, fieldValue]) => {
        conditions.push(`${fieldName} = ${fieldValueForCondition(fieldValue)}`);
        return conditions;
      }, [])
      .join(' AND ');
  }

  private getFieldList(): string[] {
    if (this.selectAll) {
      return ['FIELDS(ALL)'];
    }
    let fields = ['Id', ...Object.keys(this.condition), ...this.fields];
    const deduplicated = new Set<string>(fields);
    return Array.from(deduplicated);
  }
}
